package vivace.view;

/*
 * The skeleton for this code was excerpted from an example by Sun, and then
 * highly modified for this application. The could probably will be modified even
 * more in the future. The license is still included.
 * 
 * Copyright (c) 1999 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT
 * BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING,
 * MODIFYING OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
 * SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES,
 * HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or
 * in the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */


import java.awt.*;
import java.awt.event.*;
import java.util.Vector;


import javax.sound.midi.*;
import javax.swing.*;

public class Keyboard extends JPanel
{
   public final static Color KEY_PRESSED_COLOR = new Color (204, 204, 255);

   public final static int KEY_HEIGHT = 50, KEY_WIDTH = 16;
   public final static int ORIENTATION_HORIZONTAL = 1;
   public final static int ORIENTATION_VERTICAL = 2;
   
   private int numOctaves, orientation;
   
   private Key theKey;		// reference to pressed key


   private MidiChannel channel;


   private Synthesizer synthesizer;


   private Vector<Key> blackKeys = new Vector<Key>();
   private Vector<Key> keys = new Vector<Key>();
   private Vector<Key> whiteKeys = new Vector<Key>();

   public Keyboard (int numOctaves, int orientation, Synthesizer synthesizer)
   {
	  this.numOctaves = numOctaves;
	  this.orientation = orientation;
	  this.synthesizer = synthesizer;
	  this.channel = synthesizer.getChannels()[0];
	  

      setPreferredSize (new Dimension (numOctaves*7*KEY_WIDTH+1, KEY_HEIGHT+1));

      int transpose = 24;  
      int [] whiteIDs = { 0, 2, 4, 5, 7, 9, 11 };
 

      for (int i = 0, x = 0; i < numOctaves; i++) 
      {
           for (int j = 0; j < 7; j++, x += KEY_WIDTH) 
           {
                int keyNum = i * 12 + whiteIDs [j] + transpose;
                
                if (orientation == ORIENTATION_HORIZONTAL) {
                	whiteKeys.add (new Key (x, 0, KEY_WIDTH, KEY_HEIGHT, keyNum));
                } else {
                	whiteKeys.add (new Key (0, x, KEY_HEIGHT, KEY_WIDTH, keyNum));
                }
           }
      }

      for (int i = 0, x = 0; i < numOctaves; i++, x += KEY_WIDTH) 
      {
           int keyNum = i * 12 + transpose;

           if (orientation == ORIENTATION_HORIZONTAL) {
	           blackKeys.add (new Key ((x += KEY_WIDTH)-4, 0, KEY_WIDTH/2,
	                                   KEY_HEIGHT/2, keyNum+1));
	           blackKeys.add (new Key ((x += KEY_WIDTH)-4, 0, KEY_WIDTH/2,
	                                   KEY_HEIGHT/2, keyNum+3));
	           x += KEY_WIDTH;
	
	
	           blackKeys.add (new Key ((x += KEY_WIDTH)-4, 0, KEY_WIDTH/2,
	                          KEY_HEIGHT/2, keyNum+6));
	           blackKeys.add (new Key ((x += KEY_WIDTH)-4, 0, KEY_WIDTH/2,
	                          KEY_HEIGHT/2, keyNum+8));
	           blackKeys.add (new Key ((x += KEY_WIDTH)-4, 0, KEY_WIDTH/2,
	                          KEY_HEIGHT/2, keyNum+10));
           } else {
        	   blackKeys.add (new Key (0, (x += KEY_WIDTH)-4, 
                       KEY_HEIGHT/2, KEY_WIDTH/2, keyNum+1));
				blackKeys.add (new Key (0, (x += KEY_WIDTH)-4,
				                       KEY_HEIGHT/2, KEY_WIDTH/2, keyNum+3));
				
				x += KEY_WIDTH;
				
				
				blackKeys.add (new Key ( 0, (x += KEY_WIDTH)-4,
				              KEY_HEIGHT/2,KEY_WIDTH/2, keyNum+6));
				blackKeys.add (new Key (0, (x += KEY_WIDTH)-4, 
				              KEY_HEIGHT/2, KEY_WIDTH/2, keyNum+8));
				blackKeys.add (new Key (0, (x += KEY_WIDTH)-4, 
				              KEY_HEIGHT/2, KEY_WIDTH/2, keyNum+10));
           }
      }

      keys.addAll (blackKeys);
      keys.addAll (whiteKeys);

      this.addMouseListener(new KeyboardController());
   }
   
   public Keyboard(int numOctaves, Synthesizer synthesizer) {
	   this(numOctaves, Keyboard.ORIENTATION_HORIZONTAL, synthesizer);
   }
   
   public Keyboard(Synthesizer synthesizer) {
	   this(6, Keyboard.ORIENTATION_HORIZONTAL, synthesizer);	// default to 6 octaves
   }
 

   public void paint (Graphics g)
   {
      Graphics2D g2 = (Graphics2D) g;
      Dimension d = getSize ();


      g2.setBackground (getBackground ());
      g2.clearRect (0, 0, d.width, d.height);


      g2.setColor (Color.white);
      
      if (orientation == ORIENTATION_HORIZONTAL) {
    	  g2.fillRect (0, 0, numOctaves*7*KEY_WIDTH, KEY_HEIGHT);
      } else {
    	  g2.fillRect(0, 0, KEY_HEIGHT, numOctaves*7*KEY_WIDTH);
      }

      /* draw all white keys */
      for (int i = 0; i < whiteKeys.size (); i++)
      {
           Key key = (Key) whiteKeys.get (i);
           if (key.isNoteOn ()) 
           {
        	   // if key is pressed, fill it with another color
               g2.setColor (KEY_PRESSED_COLOR);
               g2.fill (key);
           }

           g2.setColor (Color.black);
           g2.draw (key);
      }

      /* draw all black keys */
      for (int i = 0; i < blackKeys.size (); i++) 
      {
           Key key = (Key) blackKeys.get (i);
           
           if (key.isNoteOn ()) 
           {
               // if key is pressed, fill it with another color
        	   
        	   g2.setColor (KEY_PRESSED_COLOR);

           } 
           
           g2.fill (key);
           
           g2.setColor (Color.black);
           g2.draw (key);
           
      }
   }

   private class KeyboardController implements MouseListener {
 	  public void mousePressed (MouseEvent e)
       {
            // Identify the key that was pressed. A null
            // value indicates something other than a key
            // was pressed.


            theKey = getKey (e.getPoint ());

            // If a key was pressed ...
            if (theKey != null)
            {
                theKey.on ();
                repaint ();
            }
         }


         public void mouseReleased (MouseEvent e)
         {
            if (theKey != null)
            {
                theKey.off ();
                repaint ();
            }
         }


         public void mouseExited (MouseEvent e) 
         {
            // This method is called if the mouse is moved
            // off the keyboard component. If a key was
            // pressed, we release that key.


            if (theKey != null)
            {
                // Tell key to stop playing note.


                theKey.off ();


                // Update key's visual appearance.


                repaint ();


                // The following assignment is needed so
                // that we don't execute the code within
                // mouseReleased()'s if statement should we
                // release a key after exiting the keyboard 
                // component. There is no need to tell the
                // key to stop playing a note after we have
                // already told it to do so. Furthermore,
                // we prevent an unnecessary repaint.


                theKey = null;
            }
         }
         
         public void mouseClicked(MouseEvent e) {}
         
         public void mouseEntered(MouseEvent e) {}


         public Key getKey (Point point) 
         {
            // Identify the key that was clicked.

            for (int i = 0; i < keys.size (); i++)
            { 
                 if (((Key) keys.get (i)).contains (point))
                     return (Key) keys.get (i);
            }


            return null;
         }
   }
   
   /**
    * Private class modelling a keyboard key, with information
    * about its state (ON/OFF)
    */
   private class Key extends Rectangle 
   {
	  // constant fields
      final static int ON = 0, OFF = 1, VELOCITY = 64;

      private int num;		// key number
      private int state = OFF;		// key state (ON/OFF)

      public Key (int x, int y, int width, int height, int num) 
      {
         super (x, y, width, height);
         this.num = num;
      }


      public boolean isNoteOn () 
      {
          return state == ON;
      }


      public void off () 
      {
         setState (OFF);

         if (channel != null)
             channel.noteOff (num, VELOCITY);
      }


      public void on () 
      {
         setState (ON);

         if (channel != null)
             channel.noteOn (num, VELOCITY);
      }


      public void setState (int state) 
      {
         this.state = state;
      }
   }
} 
